// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/test/aura_test_utils.h"
#include "ui/aura/test/ui_controls_factory_aura.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/test/ui_controls_aura.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/events_test_utils.h"

namespace aura {
namespace test {
    namespace {

        class UIControlsOzone : public ui_controls::UIControlsAura {
        public:
            UIControlsOzone(WindowTreeHost* host)
                : host_(host)
            {
            }

            bool SendKeyPress(gfx::NativeWindow window,
                ui::KeyboardCode key,
                bool control,
                bool shift,
                bool alt,
                bool command) override
            {
                return SendKeyPressNotifyWhenDone(
                    window, key, control, shift, alt, command, base::Closure());
            }
            bool SendKeyPressNotifyWhenDone(
                gfx::NativeWindow window,
                ui::KeyboardCode key,
                bool control,
                bool shift,
                bool alt,
                bool command,
                const base::Closure& closure) override
            {
                int flags = button_down_mask_;

                if (control) {
                    flags |= ui::EF_CONTROL_DOWN;
                    PostKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, flags);
                }

                if (shift) {
                    flags |= ui::EF_SHIFT_DOWN;
                    PostKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, flags);
                }

                if (alt) {
                    flags |= ui::EF_ALT_DOWN;
                    PostKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_MENU, flags);
                }

                if (command) {
                    flags |= ui::EF_COMMAND_DOWN;
                    PostKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_LWIN, flags);
                }

                PostKeyEvent(ui::ET_KEY_PRESSED, key, flags);
                PostKeyEvent(ui::ET_KEY_RELEASED, key, flags);

                if (alt) {
                    flags &= ~ui::EF_ALT_DOWN;
                    PostKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_MENU, flags);
                }

                if (shift) {
                    flags &= ~ui::EF_SHIFT_DOWN;
                    PostKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, flags);
                }

                if (control) {
                    flags &= ~ui::EF_CONTROL_DOWN;
                    PostKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL, flags);
                }

                if (command) {
                    flags &= ~ui::EF_COMMAND_DOWN;
                    PostKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_LWIN, flags);
                }

                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }

            bool SendMouseMove(long screen_x, long screen_y) override
            {
                return SendMouseMoveNotifyWhenDone(screen_x, screen_y, base::Closure());
            }
            bool SendMouseMoveNotifyWhenDone(
                long screen_x,
                long screen_y,
                const base::Closure& closure) override
            {
                gfx::Point root_location(screen_x, screen_y);
                aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(host_->window());
                if (screen_position_client) {
                    screen_position_client->ConvertPointFromScreen(host_->window(),
                        &root_location);
                }

                gfx::Point host_location = root_location;
                host_->ConvertPointToHost(&host_location);

                ui::EventType event_type;

                if (button_down_mask_)
                    event_type = ui::ET_MOUSE_DRAGGED;
                else
                    event_type = ui::ET_MOUSE_MOVED;

                PostMouseEvent(event_type, host_location, 0, 0);

                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }
            bool SendMouseEvents(ui_controls::MouseButton type, int state) override
            {
                return SendMouseEventsNotifyWhenDone(type, state, base::Closure());
            }
            bool SendMouseEventsNotifyWhenDone(
                ui_controls::MouseButton type,
                int state,
                const base::Closure& closure) override
            {
                gfx::Point root_location = aura::Env::GetInstance()->last_mouse_location();
                aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(host_->window());
                if (screen_position_client) {
                    screen_position_client->ConvertPointFromScreen(host_->window(),
                        &root_location);
                }

                gfx::Point host_location = root_location;
                host_->ConvertPointToHost(&host_location);

                int flag = 0;

                switch (type) {
                case ui_controls::LEFT:
                    flag = ui::EF_LEFT_MOUSE_BUTTON;
                    break;
                case ui_controls::MIDDLE:
                    flag = ui::EF_MIDDLE_MOUSE_BUTTON;
                    break;
                case ui_controls::RIGHT:
                    flag = ui::EF_RIGHT_MOUSE_BUTTON;
                    break;
                default:
                    NOTREACHED();
                    break;
                }

                if (state & ui_controls::DOWN) {
                    button_down_mask_ |= flag;
                    PostMouseEvent(ui::ET_MOUSE_PRESSED, host_location,
                        button_down_mask_ | flag, flag);
                }
                if (state & ui_controls::UP) {
                    button_down_mask_ &= ~flag;
                    PostMouseEvent(ui::ET_MOUSE_RELEASED, host_location,
                        button_down_mask_ | flag, flag);
                }

                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }
            bool SendMouseClick(ui_controls::MouseButton type) override
            {
                return SendMouseEvents(type, ui_controls::UP | ui_controls::DOWN);
            }
            void RunClosureAfterAllPendingUIEvents(
                const base::Closure& closure) override
            {
                if (!closure.is_null())
                    base::MessageLoop::current()->PostTask(FROM_HERE, closure);
            }

        private:
            void SendEventToProcessor(ui::Event* event)
            {
                ui::EventSourceTestApi event_source_test(host_->GetEventSource());
                ui::EventDispatchDetails details = event_source_test.SendEventToProcessor(event);
                if (details.dispatcher_destroyed)
                    return;
            }

            void PostKeyEvent(ui::EventType type, ui::KeyboardCode key_code, int flags)
            {
                base::MessageLoop::current()->PostTask(
                    FROM_HERE,
                    base::Bind(&UIControlsOzone::PostKeyEventTask,
                        base::Unretained(this),
                        type,
                        key_code,
                        flags));
            }

            void PostKeyEventTask(ui::EventType type,
                ui::KeyboardCode key_code,
                int flags)
            {
                // Do not rewrite injected events. See crbug.com/136465.
                flags |= ui::EF_FINAL;

                ui::KeyEvent key_event(type, key_code, flags);
                SendEventToProcessor(&key_event);
            }

            void PostMouseEvent(ui::EventType type,
                const gfx::Point& host_location,
                int flags,
                int changed_button_flags)
            {
                base::MessageLoop::current()->PostTask(
                    FROM_HERE,
                    base::Bind(&UIControlsOzone::PostMouseEventTask, base::Unretained(this),
                        type, host_location, flags, changed_button_flags));
            }

            void PostMouseEventTask(ui::EventType type,
                const gfx::Point& host_location,
                int flags,
                int changed_button_flags)
            {
                ui::MouseEvent mouse_event(type, host_location, host_location,
                    ui::EventTimeForNow(), flags,
                    changed_button_flags);

                // This hack is necessary to set the repeat count for clicks.
                ui::MouseEvent mouse_event2(&mouse_event);

                SendEventToProcessor(&mouse_event2);
            }

            WindowTreeHost* host_;

            // Mask of the mouse buttons currently down.
            unsigned button_down_mask_ = 0;

            DISALLOW_COPY_AND_ASSIGN(UIControlsOzone);
        };

    } // namespace

    ui_controls::UIControlsAura* CreateUIControlsAura(WindowTreeHost* host)
    {
        return new UIControlsOzone(host);
    }

} // namespace test
} // namespace aura
